#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <malloc.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "Buffer.h"

// Buffer manages writing and reading from the harddrive
// It buffers the input and output for speed reasons

// Windows compile workaround
#if defined(WIN32)
#include <windows.h>
long getpagesize (void) {
    static long g_pagesize = 0;
    if (! g_pagesize) {
        SYSTEM_INFO system_info;
        GetSystemInfo (&system_info);
        g_pagesize = system_info.dwPageSize;
    }
    return g_pagesize;
}
inline void *memalign(size_t blocksize, size_t bytes)
{
	return malloc(bytes);
}
#define O_DIRECT 0
#endif

#define BUFFER_SIZE (100 * (size_t)getpagesize())
#define BUFFER_ALIGN (BUFFER_SIZE * 2)

Buffer::Buffer() {
}

// opens files and prepares the write and read buffers
bool Buffer::init(const char* input, const char* output) {
	inputFile = open(input, O_DIRECT | O_RDONLY);
	if (inputFile == -1) {
		fputs("couldn't open input file\n", stderr);
		return false;
	}
	if (!initInput()) {
		return false;
	}

	outputFile = open(output, O_DIRECT | O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
	if (outputFile == -1) {
		fputs("couldn't open output file\n", stderr);
		return false;
	}
	if (!initOutput()) {
		return false;
	}
	return true;
}

Buffer::~Buffer() {
	// write everything in output buffer into file
	if(outputIndex < (BUFFER_ALIGN - 1)){
		memset(&outputBuffer[outputIndex], '\0', BUFFER_ALIGN - outputIndex);
	}
	flush();
	close(inputFile);
	close(outputFile);
	free(inputBuffer);
	free(outputBuffer);
}

bool Buffer::initInput() {
	finished = false;
	buffer1IsAlreadyChanged = false;
	buffer2IsAlreadyChanged = false;

	inputIndex = 0;
	fileIndex = 0;

	inputBuffer = (char*) memalign(BUFFER_SIZE, BUFFER_ALIGN);
	if (inputBuffer == NULL) {
		fputs("Input Buffer Memalign failed\n", stderr);
		return false;
	}

	// fill first half of buffer with content from file
	readFile = read(inputFile, &inputBuffer[inputIndex], BUFFER_SIZE);
	if (readFile == -1) {
		fputs("couldn't read from file\n", stderr);
		return false;
	}
	return true;
}

bool Buffer::initOutput() {
	outputIndex = 0;

	// why two times BUFFER_ALIGN? What about BUFFER_SIZE?
	outputBuffer = (char*) memalign(BUFFER_ALIGN, BUFFER_ALIGN);
	if (outputBuffer == NULL) {
		fputs("Output Buffer Memalign failed\n", stderr);
		return false;
	}
	return true;
}

void Buffer::writeString(const char* printString) {
	// get length of string (string must be null terminated)
	size_t size = strlen(printString);
	for (size_t i = 0; i < size; ++i) {
		if (outputIndex >= BUFFER_ALIGN) {
			// if buffer is full -> write content to file
			flush();
			--i;
			continue;
		}
		outputBuffer[outputIndex++] = printString[i];
	}
}

void Buffer::flush() {
	writeFile = write(outputFile, outputBuffer, BUFFER_ALIGN);
	outputIndex = 0;
	if (writeFile == -1) {
		fputs("couldn't write to file\n", stderr);
	}
}

bool Buffer::reloadBuffer(int position) {
	readFile = read(inputFile, &inputBuffer[position], BUFFER_SIZE);
	if (readFile == -1) {
		fputs("couldn't reload buffer\n", stderr);
		return false;
	}
	return true;
}

char Buffer::getChar() {
	inputIndex = inputIndex % BUFFER_ALIGN;
	// last sign -> end of file
	if (inputBuffer[inputIndex] == '\0') {
		finished = true;
	}
	if (!finished) {
		if (inputIndex == BUFFER_SIZE - 1) {
			if (!buffer2IsAlreadyChanged) {
				reloadBuffer(BUFFER_SIZE);
				buffer2IsAlreadyChanged = true;
				buffer1IsAlreadyChanged = false;
			}
		} else if (inputIndex == (BUFFER_SIZE * 2) - 1) {
			if (!buffer1IsAlreadyChanged) {
				reloadBuffer(0);
				buffer1IsAlreadyChanged = true;
				buffer2IsAlreadyChanged = false;
			}
		}
		++fileIndex;
		return inputBuffer[inputIndex++];
	} else {
		return '\0';
	}
}

void Buffer::ungetChar() {
	if (fileIndex > 0) {
		if (inputIndex > 0) {
			--inputIndex;
		} else if (inputIndex == 0) {
			inputIndex = (BUFFER_SIZE * 2) - 1;
		}
		--fileIndex;
		finished = false;
	}
}

void Buffer::copyString(size_t lastChars, char* position) {
	for (size_t i = 1; i <= lastChars; ++i) {
		ungetChar();
	}
	// write length of string in first position in front of string
	position[0] = lastChars;
	for (size_t i = 1; i <= lastChars; ++i) {
		position[i] = getChar();
	}
	position[lastChars + 1] = '\0'; // for easier output
}
